<?php
namespace Lavender\Dao;

use Lavender\Core;
use Lavender\Db\Expression;
use Lavender\Db\Mysql;
use Lavender\Errno;

/**
 * table access class
 * @property \Lavender\Db\Mysql $db_handle
 */
abstract class Table
{
    /**
     * database system
     * @var string
     */
    protected $driver = 'mysql';

    /**
     * database config name
     * @var string
     */
    protected $database = 'demo_database';

    /**
     * table
     * @var string
     */
    protected $table = 'demo_table';

    /**
     * first key field name
     * @var string
     */
    protected $first_key = 'id';

    /**
     * use to cache query data
     * @var array
     */
    protected $single_cache = array();

    /**
     * fields to type map
     * @var array
     */
    protected $field_definitions = array(//
    );

    /**
     * @var \Lavender\Db\Mysql
     */
    protected $db_handle;

    /***
     * @var bool 同步开关
     */
    public $is_sync = true;


    /**
     * all instances cache
     * @var array
     */
    private static $instances = array();

    public function __construct()
    {
        $this->init();
    }

    protected function init()
    {
        $this->db_handle = Core::get_database($this->driver, $this->database);
    }

    /**
     * get instance singletion
     *
     * @return Static|Table|TableDistributed
     */
    public static function instance()
    {
        $class = get_called_class();

        if (empty(self::$instances[$class])) {
            self::$instances[$class] = new $class();
        }
        return self::$instances[$class];
    }

    /**
     * multipie get
     *
     * @param mixed $ids id or id list
     * @param array $fields
     * @param array $filter
     * @param string $order
     * @param int $offset
     * @param int $length
     *
     * @return array
     */
    public function get($ids, $fields = array(), array $filter = array(), $order = null, $offset = null, $length = null,$group  = null,$having  = null)
    {
        $db = $this->get_database_instance();
        $condition = $this->build_condition($ids, $filter,$db);
        if(empty($fields)){
            $fields = [];
        }
        return $db->get($this->get_table_name(), $condition, $fields, $order, $offset, $length ,$group ,$having);
    }


    /**
     * single record get
     * @param $id
     * @param array $filter $filter filter condition
     * @param null $order
     * @return mixed
     * @throws Exception
     */
    public function get_single($id, array $filter = array(), $order = null)
    {
        $this->check_id($id);

        if (is_null($id) && empty($filter)) {
            throw new Exception("id and filter is empty", Errno::PARAM_INVALID);
        }

        // //single
        // if (isset($this->single_cache[$id]) && empty($filter) && empty($order)) {
        //     return $this->single_cache[$id];
        // }

        $db = $this->get_database_instance($id);
        $condition = $this->build_condition($id, $filter,$db);
        $items = $db->get($this->get_table_name($id), $condition, array(), $order, 1);
        if (empty($items)) {
            return null;
        }

        //cache to process cache
        // if ($id !== null  && empty($filter) && empty($order) ) {
        //     $this->single_cache[$id] = $items[0];
        // }
        return $items[0];
    }

    /**
     * single record get
     * @param array $filter $filter filter condition
     * @param null $order
     * @return mixed
     * @throws Exception
     */
    public function get_one(array $filter,array $fields = array(), $order = null)
    {
        if (empty($filter)) {
            throw new Exception("filter is empty", Errno::PARAM_INVALID);
        }

        $db = $this->get_database_instance(0);
        $condition = $this->build_condition(NULL, $filter,$db);
        $items = $db->get($this->get_table_name(0), $condition, $fields, $order, 1);
        if (empty($items)) {
            return null;
        }
        return $items[0];
    }

    /**
     * insert record
     * @param $id
     * @param array $record
     * @return boolean
     * @throws Exception
     */
    public function add($id, array $record)
    {
        if (!is_array($record)) {
            throw new Exception('param error', Errno::PARAM_INVALID);
        }

        $this->check_id($id);
        if(!empty($id))
        {
            $record[$this->first_key] = $id;
        }

        //return $this->get_database_instance($id)->insert($this->get_table_name($id), $record);
        $result = $this->get_database_instance($id)->insert($this->get_table_name($id), $record);
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }


    /**
     * insert_or_update a record
     * @param $id
     * @param array $record
     * @param  array $update
     * @return bool
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function upset($id, array $record,array $update)
    {
        if (empty($record) || !is_array($record)) {
            throw new Exception('param "$record" is empty', Errno::PARAM_INVALID);
        }

        if (empty($update) || !is_array($update)) {
            throw new Exception('param "$update" is empty', Errno::PARAM_INVALID);
        }

        $this->check_id($id);
        if($id !== null){
            $record[$this->first_key] = $id;
        }
        

        //return $this->get_database_instance($id)->insert_or_update($this->get_table_name($id), $record, $record);
        $result = $this->get_database_instance($id)->insert_or_update($this->get_table_name($id), $record, $update);
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }

    /**
     * set to a record
     * @param $id
     * @param array $record
     * @return bool
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function set($id, array $record)
    {
        if (empty($record) || !is_array($record)) {
            throw new Exception('param "$record" is empty', Errno::PARAM_INVALID);
        }

        $this->check_id($id);
        if($id !== null){
            $record[$this->first_key] = $id;
        }

        //return $this->get_database_instance($id)->insert_or_update($this->get_table_name($id), $record, $record);
        $result = $this->get_database_instance($id)->insert_or_update($this->get_table_name($id), $record, $record);
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }

    /**
     * update a record
     * @param $id
     * @param $record
     * @param array $filter
     * @return int
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function update($id, $record, array $filter = array())
    {
        $this->check_id($id);
        $db = $this->get_database_instance($id);
        $condition = $this->build_condition($id, $filter,$db);
        $db->update($this->get_table_name($id), $record, $condition);

        //return $this->get_database_instance($id)->get_affected_rows();
        $result = $db->get_affected_rows();
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }

    /**
     * @param $ids
     * @param $record
     * @param array $filter
     * @return int
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function update_all($ids,$record,array $filter = array())
    {
        if(empty($record))
        {
            return 0;
        }
        $db = $this->get_database_instance();
        $condition = $this->build_condition($ids, $filter,$db);
        $db->update($this->get_table_name(), $record, $condition);

        //return $this->get_database_instance($id)->get_affected_rows();
        $result = $db->get_affected_rows();
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }

    /**
     * delete
     * @param $id
     * @param array $filter
     * @return int
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function delete($id, array $filter = array())
    {
        $this->check_id($id);
        $db = $this->get_database_instance($id);
        $condition = $this->build_condition($id, $filter,$db);
        $db->delete($this->get_table_name($id), $condition);

        //return $this->get_database_instance($id)->get_affected_rows();
        $result = $db->get_affected_rows();
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }

    /**
     * @param $ids
     * @param array $filter
     * @return int
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function delete_all($ids, array $filter = array())
    {
        $db = $this->get_database_instance();
        $condition = $this->build_condition($ids, $filter,$db);
        if(empty($condition))
        {
            throw new Exception('delete all condition can not be empty', Errno::PARAM_INVALID);
        }
        $db->delete($this->get_table_name(), $condition);
        //return $this->get_database_instance($id)->get_affected_rows();
        $result = $db->get_affected_rows();
        //数据同步
        self::set_sync_data(get_called_class(), __FUNCTION__, func_get_args());
        return $result;
    }

    /**
     * @param $id
     * @param $increments
     * @param array $update_item
     * @param array $create_item
     * @return int
     * @throws Exception
     * @throws \Lavender\Db\Exception
     */
    public function increment($id, array $increments = [], array $update_item = [] , array $create_item = [])
    {
        $insert_item = [];
        if(count($increments)<1)
        {
            throw new Exception("increment item can not be empty's value invalid", Errno::PARAM_INVALID);
        }
        if(!empty($id))
        {
            $insert_item[$this->first_key] = $id;
        } 
        foreach($increments as $field => $num)
        {
            //check field
            if (empty($field) || !is_string($field) || !$this->get_database_instance($id)->check_name($field)) {
                throw new Exception('param error,field ({$field}) invalid', Errno::PARAM_INVALID);
            }
            if (!is_int($num)) {
                throw new Exception("param error,field ({$num}) invalid", Errno::PARAM_INVALID);
            }
            $insert_item[$field] = $num;
            $opera = $num >= 0 ? "+":"-";
            $change = abs($num);
            $update_item[$field] = Expression::MakeRaw("`{$field}` {$opera} {$change}");
        }
        $insert_item =array_merge($create_item,$insert_item);
        $db = $this->get_database_instance($id);
        $db->insert_or_update($this->get_table_name($id),$insert_item,$update_item);
        return $db->get_affected_rows();
    }

    /**
     * count
     * @param $id
     * @param array $filter
     * @param $field
     * @return int
     * @throws Exception
     */
    public function count($id, array $filter = array(),$field = '*')
    {
        $this->check_id($id);
        $db = $this->get_database_instance($id);
        $condition = $this->build_condition($id, $filter,$db);
        return $db->count($this->get_table_name($id), $condition,$field);
    }

    /**
     * build condition by id & filter
     * @param $id
     * @param array $filter
     * @param Mysql $db
     * @return string
     * @throws Exception
     */
    protected function build_condition($id, array $filter, Mysql $db = null)
    {
        $condition_items = array();
        if(empty($db))
        {
            $db = $this->get_database_instance();
        }

        if ($id !== null) {
            if (is_array($id)) {
                $id = array_map('intval', $id);
                $condition_items[] = "`{$this->first_key}` IN ('" . implode("','", $id) . "')";
            } else {
                $condition_items[] = "`{$this->first_key}` = " . intval($id);
            }
        }

        if (!empty($filter)) {
            foreach ($filter as $k => $v) {
                if($v instanceof Expression)
                {
                    $condition_items[] = "( ".$v->getSql($db)." )";
                }
                else{
                    if (!$db->check_name($k)) {
                        throw new Exception("filter field name verify failed,name:'{$k}'", Errno::PARAM_INVALID);
                    }
                    else
                    {
                        $v = $db->escape($v);
                        $condition_items[] = "`{$k}`='{$v}'";
                    }
                }

            }
        }
        return implode(' AND ', $condition_items);
    }

    /**
     * @param int $id
     * @return \Lavender\Db\Mysql
     * @throws \Lavender\Exception
     */
    public function get_database_instance($id = 0)
    {
        return $this->db_handle;
    }

    protected function get_database_id($id)
    {
        return null;
    }

    /**
     * 返回表名
     * @param int $id
     * @return string
     */
    public function get_table_name($id = 0)
    {
        return $this->table;
    }

    protected function check_id($id)
    {
        if ($id !== null && !is_numeric($id)) {
            throw new Exception("first_key's value invalid", Errno::PARAM_INVALID);
        }
    }

    /**
     * 返回主键
     * @return string
     */
    public function get_first_key()
    {
        return $this->first_key;
    }

    /*author huaqiu.zou
    *  设置first_key
    *  返回自身。连贯操作
     *
    */
    public function set_first_key($first_key)
    {
        $this->first_key = $first_key;
        return $this;
    }

    /*author huaqiu.zou
     * 返回最后一条sql语句
     */

    public function get_last_sql()
    {

        return $this->db_handle->last_sql;
    }

    /*author huaqiu.zou
     * 使用原生sql语句
     */
    public function get_by_sql($sql, $offset = null, $length = null)
    {
        return $this->db_handle->get_by_sql($sql, $offset, $length);
    }

    protected function get_bulk_insert_sql(array $items){   
        if(isset($items[0][$this->first_key])){
            $id = $items[0][$this->first_key];
        }
        else{
            $id = 0;
        }
        $prefix = "INSERT INTO `".$this->get_table_name($id)."` (`".implode('`,`',array_keys($items[0]))."`) VALUES ";
        $valueItems = [];
        foreach($items as $item){
            $valueItems[] = "('".implode("','",array_values($item))."')";
        }
        $sql = $prefix.implode(",",$valueItems).";";
        return $sql;
    }

    /**
     * 批量插入
     * @param array $items
     * @param int $piece 每段数量
     * @return int
     */
    public function bulk_insert(array $items,$piece = 300){
        $num = count($items);        
        if($num < 1){
            return 0;
        }else{
            $prefix = "INSERT INTO `".$this->table."` (`".implode('`,`',array_keys($items[0]))."`) VALUES ";
            $index = 0;
            do{
                    $pieceItem = array_slice($items,$index * $piece,$piece);
                    if(count($pieceItem) < 1){
                        break;
                    }
                    $valueItems = [];
                    foreach($pieceItem as $item){
                        $valueItems[] = "('".implode("','",array_values($item))."')";
                    }
                    $sql = $prefix.implode(",",$valueItems).";";
                    $this->get_database_instance()->execute($sql);
                    $index++;
                }while(count($pieceItem) >= $piece );
            return  $num;
        }
    }

    /*
     * author cheng.qian
     * 设置表名
     *
     */
    public function set_table_name($table_name)
    {
        $this->table = $table_name;
        return $this;
    }

    protected function  set_sync_data($called_class, $function, array $params)
    {
        //数据同步
//        if ($this->db_handle->get_affected_rows()){
//            if ($this->is_sync) {
//                $sync_table=Core::get_config('const','sync_table');
//                if ($sync_table && in_array($called_class,$sync_table)){
//                    \Golo\Api\DataSync::send(L_APP_NAME,$called_class,$function, $params);
//                }
//            }
//       }
    }

}
